/**
 * Checknumber.java
 * uAPI Public License
 */
package uapi.bo.checknumbers;

/**
 * Representing a german identity card. It can be use to check the identity
 * card and get the included birthday. It can also use for a simple age
 * verification system but all you can check are the checknumbers not the
 * person identity.
 *
 * @author Bastie - Sebastian Ritter
 * @version 1.0
 */
public final class GermanIdentityCard extends IdentityCard {

  /**
   * Character to fill unused space and separates fields
   */
  private final char FILLER_SEPARATOR = '<';

  /**
   * Internal field
   */
  private enum FIELDS {GOV, COUNT, CHECKNUMBER_1, COUNTRY, BIRTHDAY, CHECKNUMBER_2, LAST_DAY, CHECKNUMBER_3, CHECKNUMBER_ALL};

  /** last line of identity card */
  private String idCardNumber;
  /** Default constructor */
  public GermanIdentityCard () {}
  /** Construct object and set the last line of identity card */
  public GermanIdentityCard (final String idCardNumber) {
    this.setIdCardNumber(idCardNumber);
  }
  /** Set the last line of identity card. This line includes some information
   * about person.
   * @param lastFrontSiteLineWithLesserThen full line
   */
  public final void setIdCardNumber (final String lastFrontSiteLineWithLesserThen) {
    this.idCardNumber = lastFrontSiteLineWithLesserThen;
  }

  /**
   * Return the last line od identity card.
   * @return String
   */
  public final String getIdCardNumber () {
    return this.idCardNumber;
  }

  public final boolean validate () {
    try {
      int checknumber_all = 0;
      int temp;
      // Step 1 no card number property set?
      if (this.idCardNumber == null)return false;

      // Step 2 card number property with right lengt
      if (this.getIdCardNumber ().length () != 36)return false;

      // Step 3 validate checknumber for gouverment and count number
      final String gc_number = this.getField (FIELDS.GOV) + this.getField (FIELDS.COUNT);
      int checknumber = 0;
      temp = Integer.parseInt (gc_number.substring (0, 1)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (1, 2)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (2, 3)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (3, 4)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (4, 5)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (5, 6)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (6, 7)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (7, 8)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      temp = Integer.parseInt (gc_number.substring (8, 9)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;

      checknumber_all += checknumber;
      while (checknumber > 9) checknumber -= 10;
      if (Integer.parseInt (this.getField (FIELDS.CHECKNUMBER_1)) != checknumber)return false;
      temp = checknumber * 7;
      while (checknumber > 9) checknumber -= 10;
      checknumber_all += checknumber;

      // Step 4 is german id card
      if ('D' != this.getField (FIELDS.COUNTRY).charAt (0))return false;

      // Step 5 is valid birthday
      // Step 5.1 is real date
      final String birthday = this.getField (FIELDS.BIRTHDAY);
      if (!this.isRealDate(birthday, false)) return false;
      // Step 5.2 is birthday valid
      temp = 0;
      checknumber = 0;
      temp = Integer.parseInt (birthday.substring (0, 1)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 3;
      temp = Integer.parseInt (birthday.substring (1, 2)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 1;
      temp = Integer.parseInt (birthday.substring (2, 3)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 7;
      temp = Integer.parseInt (birthday.substring (3, 4)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 3;
      temp = Integer.parseInt (birthday.substring (4, 5)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 1;
      temp = Integer.parseInt (birthday.substring (5, 6)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 7;

      while (checknumber > 9) checknumber -= 10;
      if (Integer.parseInt (this.getField (FIELDS.CHECKNUMBER_2)) != checknumber)return false;
      temp = checknumber * 3;
      while (checknumber > 9) checknumber -= 10;
      checknumber_all += checknumber;

      // Step 6 last valid day
      // Step 6.1 is real date
      final String date = this.getField (FIELDS.LAST_DAY);
      if (!this.isRealDate(date, false)) return false;
      // Step 6.2 is date valid
      temp = 0;
      checknumber = 0;
      temp = Integer.parseInt (date.substring (0, 1)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 1;
      temp = Integer.parseInt (date.substring (1, 2)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 7;
      temp = Integer.parseInt (date.substring (2, 3)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 3;
      temp = Integer.parseInt (date.substring (3, 4)) * 7;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 1;
      temp = Integer.parseInt (date.substring (4, 5)) * 3;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 7;
      temp = Integer.parseInt (date.substring (5, 6)) * 1;
      while (temp > 9) temp -= 10;
      checknumber += temp;
      checknumber_all += temp * 3;

      while (checknumber > 9) checknumber -= 10;
      if (Integer.parseInt (this.getField (FIELDS.CHECKNUMBER_3)) != checknumber)return false;
      temp = checknumber * 1;
      while (checknumber > 9) checknumber -= 10;
      checknumber_all += checknumber;

      while (checknumber_all > 9) checknumber_all -= 10;
      return Integer.parseInt (this.getField (FIELDS.CHECKNUMBER_ALL)) == checknumber_all;
    }
    catch (final RuntimeException ignoreAndReturnInvalid) { // need f.e. for IllegalNumberFormat
      return false;
    }
  }

  /**
   * Check the given date for existing date. F.e. 2001-02-29 return false,
   * 2000-02-29 returns true, 2001-13-29 returns false
   * @param YYMMDD String
   * @param monthBaseIsZero tells the given month begins at zero for january or one for january
   * @return boolean
   */
  private boolean isRealDate (final String YYMMDD, boolean monthBaseIsZero) {
    final int year = Integer.parseInt (YYMMDD.substring (0, 2));
    final int month = (monthBaseIsZero) ? Integer.parseInt (YYMMDD.substring (2, 4)) + 1 : Integer.parseInt (YYMMDD.substring (2, 4));
    final int day = Integer.parseInt (YYMMDD.substring (4));
    if (day < 1) return false;
    if (month < 1 || month > 12) return false;
    switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
      if (day > 31)return false;
    case 4:
    case 6:
    case 9:
    case 11:
      if (day > 30)return false;
    case 2:
      if (year % 1000 == 0 && day > 29)return false;
      if (year % 100 == 0 && day > 28)return false;
      if (year % 4 == 0 && day > 29)return false;
      if (day > 28)return false;
    }
    return true;
  }

  /**
   * Return the include birthday in natural format (month base is one) or empty
   * string if it is not a valid date.
   * <br><strong>Note:</strong>It doesn't validate the identity card only
   * the birthday.
   * @return String birthday
   */
  public String getBirthdayYYMMDD () {
    return (this.isRealDate(this.getField(FIELDS.BIRTHDAY), false)) ? this.getField(FIELDS.BIRTHDAY) : "";
  }

  /**
   * Returns the included fields.
   * @param index FIELDS
   * @return String
   */
  private final String getField (final FIELDS index) {
    String value = "";
    switch (index) {
    case GOV :
      value = this.getIdCardNumber().substring(0,4);
      break;
    case COUNT :
      value = this.getIdCardNumber().substring(4,9);
      break;
    case CHECKNUMBER_1 :
      value = this.getIdCardNumber().substring(9,10);
      break;
    case COUNTRY :
      value = this.getIdCardNumber().substring(10,11);
      break;
    case BIRTHDAY :
      value = this.getIdCardNumber().substring(13,19);
      break;
    case CHECKNUMBER_2 :
      value = this.getIdCardNumber().substring(19,20);
      break;
    case LAST_DAY :
      value = this.getIdCardNumber().substring(21,27);
      break;
    case CHECKNUMBER_3 :
      value = this.getIdCardNumber().substring(27,28);
      break;
    case CHECKNUMBER_ALL :
      value = this.getIdCardNumber().substring(35);
      break;
    }
    return value;
  }

}
